import type { QRCodeRenderersOptions } from "qrcode";
import type { PropType } from "vue";
import { cloneDeep, isString } from "es-toolkit";
import QRCode from "qrcode";
import { computed, defineComponent, nextTick, ref, unref, watch } from "vue";
import RefreshRight from "~icons/ep/refresh-right";
import "./index.scss";

interface QrcodeLogo {
    src?: string;
    logoSize?: number;
    bgColor?: string;
    borderSize?: number;
    crossOrigin?: string;
    borderRadius?: number;
    logoRadius?: number;
}

interface Props {
    tag?: "canvas" | "img";
    value?: string | Record<string, any>[];
    options?: QRCodeRenderersOptions;
    width?: number;
    icon?: Partial<QrcodeLogo> | string;
    disabled?: boolean;
    disabledText?: string;
}

const props = {
    // img 或者 canvas,img不支持logo嵌套，但使用image绘制可以避免某些跨域情况
    tag: {
        type: String as PropType<"canvas" | "img">,
        default: "canvas",
    },
    // 二维码内容
    value: {
        type: [String, Array] as PropType<string | Record<string, any>[]>,
        default: null,
    },
    // qrcode.js配置项
    options: {
        type: Object as PropType<QRCodeRenderersOptions>,
        default: (): QRCodeRenderersOptions => ({}),
    },
    // 宽度
    width: {
        type: Number,
        default: 200,
    },
    // 二维码中间图片
    icon: {
        type: [String, Object] as PropType<Partial<QrcodeLogo> | string>,
        default: (): QrcodeLogo | string => "",
    },
    // 是否过期
    disabled: {
        type: Boolean,
        default: false,
    },
    // 过期提示内容
    disabledText: {
        type: String,
        default: "",
    },
};

export default defineComponent<Props>({
    name: "ReQrcode",
    props,
    emits: ["done", "click", "disabled-click"],
    setup(props, { emit }) {
        const { toCanvas, toDataURL } = QRCode;
        const loading = ref(true);
        const wrapRef = ref<HTMLCanvasElement | HTMLImageElement | null>(null);
        const renderText = computed(() => String(props.value));
        const wrapStyle = computed(() => {
            return {
                width: `${props.width}px`,
                height: `${props.width}px`,
            };
        });
        const initQrcode = async (): Promise<void> => {
            await nextTick();
            const options = cloneDeep(props.options || {});
            if (props.tag === "canvas") {
                // 容错率，默认对内容少的二维码采用高容错率，内容多的二维码采用低容错率
                options.errorCorrectionLevel
                    = options.errorCorrectionLevel || getErrorCorrectionLevel(unref(renderText));
                const _width: number = await getOriginWidth(unref(renderText), options);
                options.scale = props.width === 0 ? undefined : (props.width! / _width) * 4;
                const canvasRef: any = await toCanvas(
                    unref(wrapRef) as HTMLCanvasElement,
                    unref(renderText),
                    options,
                );
                if (props.icon) {
                    const url = await createLogoCode(canvasRef);
                    emit("done", url);
                    loading.value = false;
                } else {
                    emit("done", canvasRef.toDataURL());
                    loading.value = false;
                }
            } else {
                const url = await toDataURL(renderText.value, {
                    errorCorrectionLevel: "H",
                    width: props.width,
                    ...options,
                });
                (unref(wrapRef) as any).src = url;
                emit("done", url);
                loading.value = false;
            }
        };
        watch(
            () => renderText.value,
            (val) => {
                if (!val) return;
                initQrcode();
            },
            {
                deep: true,
                immediate: true,
            },
        );
        const createLogoCode = (canvasRef: HTMLCanvasElement): Promise<any> | undefined => {
            const canvasWidth = canvasRef.width;
            const logoOptions: QrcodeLogo = Object.assign(
                {
                    logoSize: 0.15,
                    bgColor: "#ffffff",
                    borderSize: 0.05,
                    crossOrigin: "anonymous",
                    borderRadius: 8,
                    logoRadius: 0,
                },
                isString(props.icon) ? {} : props.icon,
            );
            const {
                logoSize = 0.15,
                bgColor = "#ffffff",
                borderSize = 0.05,
                crossOrigin = "anonymous",
                borderRadius = 8,
                logoRadius = 0,
            } = logoOptions;
            const logoSrc = isString(props.icon) ? props.icon : props.icon?.src;
            const logoWidth = canvasWidth * logoSize;
            const logoXY = (canvasWidth * (1 - logoSize)) / 2;
            const logoBgWidth = canvasWidth * (logoSize + borderSize);
            const logoBgXY = (canvasWidth * (1 - logoSize - borderSize)) / 2;
            const ctx = canvasRef.getContext("2d");
            if (!ctx) return;
            // logo 底色
            canvasRoundRect(ctx)(logoBgXY, logoBgXY, logoBgWidth, logoBgWidth, borderRadius);
            ctx.fillStyle = bgColor;
            ctx.fill();
            // logo
            const image = new Image();
            if (crossOrigin || logoRadius) {
                image.setAttribute("crossOrigin", crossOrigin);
            }
            (image as any).src = logoSrc;
            // 使用image绘制可以避免某些跨域情况
            const drawLogoWithImage = (image: HTMLImageElement): void => {
                ctx.drawImage(image, logoXY, logoXY, logoWidth, logoWidth);
            };
            // 使用canvas绘制以获得更多的功能
            const drawLogoWithCanvas = (image: HTMLImageElement): void => {
                const canvasImage = document.createElement("canvas");
                canvasImage.width = logoXY + logoWidth;
                canvasImage.height = logoXY + logoWidth;
                const imageCanvas = canvasImage.getContext("2d");
                if (!imageCanvas || !ctx) return;
                imageCanvas.drawImage(image, logoXY, logoXY, logoWidth, logoWidth);
                canvasRoundRect(ctx)(logoXY, logoXY, logoWidth, logoWidth, logoRadius);
                if (!ctx) return;
                const fillStyle = ctx.createPattern(canvasImage, "no-repeat");
                if (fillStyle) {
                    ctx.fillStyle = fillStyle;
                    ctx.fill();
                }
            };
            // 将 logo绘制到 canvas上
            return new Promise((resolve: any) => {
                image.onload = () => {
                    logoRadius ? drawLogoWithCanvas(image) : drawLogoWithImage(image);
                    resolve(canvasRef.toDataURL());
                };
            });
        };
        // 得到原QrCode的大小，以便缩放得到正确的QrCode大小
        const getOriginWidth = async (content: string, options: QRCodeRenderersOptions): Promise<number> => {
            const _canvas = document.createElement("canvas");
            await toCanvas(_canvas, content, options);
            return _canvas.width;
        };
        // 对于内容少的QrCode，增大容错率
        const getErrorCorrectionLevel = (content: string): "M" | "Q" | "H" => {
            if (content.length > 36) {
                return "M";
            } else if (content.length > 16) {
                return "Q";
            } else {
                return "H";
            }
        };
        // 用于绘制圆角
        const canvasRoundRect = (ctx: CanvasRenderingContext2D): ((x: number, y: number, w: number, h: number, r: number) => CanvasRenderingContext2D) => {
            return (x: number, y: number, w: number, h: number, r: number): CanvasRenderingContext2D => {
                const minSize = Math.min(w, h);
                if (r > minSize / 2) {
                    r = minSize / 2;
                }
                ctx.beginPath();
                ctx.moveTo(x + r, y);
                ctx.arcTo(x + w, y, x + w, y + h, r);
                ctx.arcTo(x + w, y + h, x, y + h, r);
                ctx.arcTo(x, y + h, x, y, r);
                ctx.arcTo(x, y, x + w, y, r);
                ctx.closePath();
                return ctx;
            };
        };
        const clickCode = (): void => {
            emit("click");
        };
        const disabledClick = (): void => {
            emit("disabled-click");
        };
        return () => (
            <>
                <div
                    v-loading={unref(loading)}
                    class="qrcode relative inline-block"
                    style={unref(wrapStyle)}
                >
                    {props.tag === "canvas"
                        ? (
                                <canvas ref={wrapRef} onClick={clickCode}></canvas>
                            )
                        : (
                                <img ref={wrapRef} onClick={clickCode}></img>
                            )}
                    {props.disabled && (
                        <div
                            class="qrcode--disabled absolute top-0 left-0 flex w-full h-full items-center justify-center"
                            onClick={disabledClick}
                        >
                            <div class="absolute top-[50%] left-[50%] font-bold">
                                <iconify-icon-offline
                                    class="cursor-pointer"
                                    icon={RefreshRight}
                                    width="30"
                                    color="var(--el-color-primary)"
                                />
                                <div>{props.disabledText}</div>
                            </div>
                        </div>
                    )}
                </div>
            </>
        );
    },
});
